package gui;

import javax.swing.*;

import controllers.PIDParameters;
import controllers.PIParameters;


import java.awt.*;
import java.awt.event.*;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.ArrayList;

import regulators.BallAndBeamRegul;
import regulators.BeamRegul;
import regulators.Regul;
import se.lth.control.*;
import se.lth.control.plot.*;
import stateMachine.StateMachine;

/** Class that creates and maintains a GUI for the Ball and Beam process.
Uses two internal threads to update plotters */
public class OpCom {
	public static final int OFF = 0, BEAM = 1, BALL = 2;

	private BeamRegul beamRegul;
	private PIDParameters innerPar;
	private PIDParameters outerPar;
	private int mode;

	private PlotterPanel measurementPlotter; // has internal thread
	private PlotterPanel controlPlotter; // has internal thread
	private PlotterPanel anglePlotter; // has internal thread
	private PlotterPanel posPlotter; // has internal thread

	// Declarartion of main frame.
	private JFrame frame;
	boolean start;

	// Declarartion of panels.
	private BoxPanel guiPanel, plotterPanel, innerParPanel, outerParPanel, buttonPanel, extendedPanel,outerParButtonPanel;
	private JPanel innerParLabelPanel, innerParFieldPanel, outerParLabelPanel, outerParFieldPanel, leftPanel,extendedParPanel, extendedNamePanel, rightPanel;

	// Declaration of components.
	private DoubleField innerParKField = new DoubleField(5,3);
	private DoubleField innerParTiField = new DoubleField(5,3);
	private DoubleField innerParTrField = new DoubleField(5,3);
	private DoubleField innerParBetaField = new DoubleField(5,3);
	private DoubleField innerParHField = new DoubleField(5,3);
	private DoubleField innerParRefField = new DoubleField(5,3);
	private JButton innerApplyButton;


	private JLabel console;
	private JTextArea consoelWindow;
	private JPanel consolePanel;



	private JLabel currentAngle = new JLabel();
	private JLabel currentStep = new JLabel();
	private JLabel weight = new JLabel();



	private DoubleField outerParKField = new DoubleField(5,3);
	private DoubleField outerParTiField = new DoubleField(5,3);
	private DoubleField outerParTdField = new DoubleField(5,3);
	private DoubleField outerParTrField = new DoubleField(5,3);
	private DoubleField outerParNField = new DoubleField(5,3);
	private DoubleField outerParBetaField = new DoubleField(5,3);
	private DoubleField outerParHField = new DoubleField(5,3);
	private JButton outerApplyButton;

	//private JRadioButton beamAndBallButton;
	private JButton resetButton;
	// private JRadioButton autoMode;
	// private JRadioButton manuelMode;
	private JButton stopButton;

	private boolean isAuto;
	private double range = 10.0; // Range of time axis
	private int divTicks = 5;    // Number of ticks on time axis
	private int divGrid = 5;     // Number of grids on time axis

	private boolean hChanged = false;

	private JScrollPane scroll;

	private StateMachine stateMachine;

	private BallAndBeamRegul ballAndBeamRegul;

	//private JButton stepButton;

	/** Constructor. Creates the plotter panels. */
	public OpCom() {
		measurementPlotter = new PlotterPanel(2, Thread.MAX_PRIORITY);
		controlPlotter = new PlotterPanel(1, Thread.MAX_PRIORITY-1);
		anglePlotter = new PlotterPanel(1, Thread.MAX_PRIORITY);
		posPlotter = new PlotterPanel(1, Thread.MAX_PRIORITY);
		start = false;
		isAuto = true;
	}

	/** Starts the threads. */
	public void start() {
		measurementPlotter.start();
		controlPlotter.start();
		anglePlotter.start();
		posPlotter.start();

	}

	/** Stops the threads. */
	public void stopThread() {
		measurementPlotter.stopThread();
		controlPlotter.stopThread();
		anglePlotter.stopThread();
		posPlotter.stopThread();
	}


	/** Sets up a reference to Regul. Called by Main. 
	 * @param ballAndBeam */
	public void setRegul(BeamRegul beam, BallAndBeamRegul ballAndBeam) {
		ballAndBeamRegul = ballAndBeam;
		beamRegul = beam;
	}

	/** Creates the GUI. Called from Main. */
	public void initializeGUI(int i) { // 1= beam, 2 = BallAndBeam
		// Create main frame.
		if( i == 0){
			frame = new JFrame("Ball and Beam GUI");

			// Create a panel for the two plotters.
			plotterPanel = new BoxPanel(BoxPanel.VERTICAL);
			// Create plot components and axes, add to plotterPanel.
			measurementPlotter.setYAxis(20, -10, 2, 2);
			measurementPlotter.setXAxis(range, divTicks, divGrid);
			measurementPlotter.setTitle("Set-point and measured variable");
			plotterPanel.add(measurementPlotter);
			plotterPanel.addFixed(10);
			controlPlotter.setYAxis(20, -10, 2, 2);
			controlPlotter.setXAxis(range, divTicks, divGrid);
			controlPlotter.setTitle("Control");
			plotterPanel.add(controlPlotter);

			anglePlotter.setYAxis(20, -10, 2, 2);
			anglePlotter.setXAxis(range, divTicks, divGrid);
			anglePlotter.setTitle("Angle plot");
			plotterPanel.add(anglePlotter);
			posPlotter.setYAxis(20, -10, 2, 2);
			posPlotter.setXAxis(range, divTicks, divGrid);
			posPlotter.setTitle("Position plot");
			plotterPanel.add(posPlotter);

			// Create panel for the buttons.
			buttonPanel = new BoxPanel(BoxPanel.VERTICAL);
			// Create the buttons.
			resetButton = new JButton("RESET");
			stopButton = new JButton("STOP");

			ButtonGroup group = new ButtonGroup();
			group.add(resetButton);
			
//			resetButton.addActionListener(new ActionListener() {
//				public void actionPerformed(ActionEvent e) {
//					if(!start){
//						stateMachine.resetStateMachine();
//						start = true;
//						resetButton.setText("START");                        		 
//						writeToConsole("Please reset the machine if necessary before pressing START");
//					}else{               
//						stateMachine.resetStart();
//						start = false;
//						resetButton.setText("RESET");
//					}
//
//				}
//			});
			stopButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					writeToFile();
					ballAndBeamRegul.shutDown();
					stopThread();
					System.exit(0);
				}

				private void writeToFile() {
					ArrayList<double[]> temp = stateMachine.getTracking();
					ArrayList<Double> temp2 = stateMachine.getMean();

					try{
						// Create file 
						FileWriter fstream = new FileWriter("out.txt");
						BufferedWriter out = new BufferedWriter(fstream);
						int counter = 0;
						for(double[] i: temp){
							counter ++;
							out.write("Messurement " + counter + ": " );
							for(double j: i){
								out.write(j + " ");
							}
							out.write("\n\r");
							double a  = temp2.get(counter - 1);
							out.write("Mean Value: " + a);
							out.write("\n\r");
						}
						//Close the output stream
						out.close();
					}catch (Exception e){//Catch exception if any
						System.err.println("Error: " + e.getMessage());
					}

				}
			});

			buttonPanel.add(stopButton);
			// Create panel holding everything but the plotters.
			leftPanel = new JPanel();
			leftPanel.setLayout(new BorderLayout());
			leftPanel.add(buttonPanel, BorderLayout.SOUTH);

			// Create panels for the parameter fields and labels, add labels and fields
			innerParPanel = new BoxPanel(BoxPanel.HORIZONTAL);
			innerParLabelPanel = new JPanel();
			innerParLabelPanel.setLayout(new GridLayout(0,1));
			innerParLabelPanel.add(new JLabel("K: "));
			innerParLabelPanel.add(new JLabel("Ti: "));
			innerParLabelPanel.add(new JLabel("Tr: "));
			innerParLabelPanel.add(new JLabel("Beta: "));
			innerParLabelPanel.add(new JLabel("h: "));
			innerParLabelPanel.add(new JLabel("Ref: "));
			innerParFieldPanel = new JPanel();
			innerParFieldPanel.setLayout(new GridLayout(0,1));
			innerParFieldPanel.add(innerParKField);
			innerParFieldPanel.add(innerParTiField);
			innerParFieldPanel.add(innerParTrField);
			innerParFieldPanel.add(innerParBetaField);
			innerParFieldPanel.add(innerParHField);
			innerParFieldPanel.add(innerParRefField);

			innerParKField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					innerPar.K = innerParKField.getValue();
					innerApplyButton.setEnabled(true);
				}
			});
			innerParTiField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					innerPar.Ti = innerParTiField.getValue();
					if (innerPar.Ti==0.0) {
						innerPar.integratorOn = false;
					}
					else {
						innerPar.integratorOn = true;
					}
					innerApplyButton.setEnabled(true);
				}
			});
			innerParTrField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					innerPar.Tr = innerParTrField.getValue();
					innerApplyButton.setEnabled(true);
				}
			});
			innerParBetaField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					innerPar.Beta = innerParBetaField.getValue();
					innerApplyButton.setEnabled(true);
				}
			});
			innerParHField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					innerPar.H = innerParHField.getValue();
					outerPar.H = innerPar.H;
					outerParHField.setValue(innerPar.H);
					innerApplyButton.setEnabled(true);
					hChanged = true;
				}
			});

			innerParRefField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					ballAndBeamRegul.setRef(innerParRefField.getValue());
					//innerApplyButton.setEnabled(true);
				}
			});







			innerParPanel.add(innerParLabelPanel);
			innerParPanel.addGlue();
			innerParPanel.add(innerParFieldPanel);
			innerParPanel.addFixed(10);


			// Create apply button and action listener.
			innerApplyButton = new JButton("Apply");
			innerApplyButton.setEnabled(false);
			innerApplyButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					ballAndBeamRegul.setInnerParameters(innerPar);
					if (hChanged) {
						ballAndBeamRegul.setOuterParameters(outerPar);
					}      
					hChanged = false;
					innerApplyButton.setEnabled(false);
				}
			});

			// Create panel with border to hold apply button and parameter panel
			BoxPanel innerParButtonPanel = new BoxPanel(BoxPanel.VERTICAL);
			innerParButtonPanel.setBorder(BorderFactory.createTitledBorder("Inner Parameters"));
			innerParButtonPanel.addFixed(10);
			innerParButtonPanel.add(innerParPanel);
			innerParButtonPanel.addFixed(10);
			innerParButtonPanel.add(innerApplyButton);

			// The same as above for the outer parameters
			outerParPanel = new BoxPanel(BoxPanel.HORIZONTAL);
			outerParLabelPanel = new JPanel();
			outerParLabelPanel.setLayout(new GridLayout(0,1));
			outerParLabelPanel.add(new JLabel("K: "));
			outerParLabelPanel.add(new JLabel("Ti: "));
			outerParLabelPanel.add(new JLabel("Td: "));
			outerParLabelPanel.add(new JLabel("N: "));
			outerParLabelPanel.add(new JLabel("Tr: "));
			outerParLabelPanel.add(new JLabel("Beta: "));
			outerParLabelPanel.add(new JLabel("h: "));

			outerParFieldPanel = new JPanel();
			outerParFieldPanel.setLayout(new GridLayout(0,1));
			outerParFieldPanel.add(outerParKField);
			outerParFieldPanel.add(outerParTiField);
			outerParFieldPanel.add(outerParTdField);
			outerParFieldPanel.add(outerParNField);
			outerParFieldPanel.add(outerParTrField);
			outerParFieldPanel.add(outerParBetaField);
			outerParFieldPanel.add(outerParHField);
			outerParKField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					outerPar.K = outerParKField.getValue();
					outerApplyButton.setEnabled(true);
				}
			});
			outerParTiField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					outerPar.Ti = outerParTiField.getValue();
					if (outerPar.Ti==0.0) {
						outerPar.integratorOn = false;
					}
					else {
						outerPar.integratorOn = true;
					}
					outerApplyButton.setEnabled(true);
				}
			});
			outerParTdField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					outerPar.Td = outerParTdField.getValue();
					outerApplyButton.setEnabled(true);
				}
			});
			outerParNField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					outerPar.N = outerParNField.getValue();
					outerApplyButton.setEnabled(true);
				}
			});
			outerParTrField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					outerPar.Tr = outerParTrField.getValue();
					outerApplyButton.setEnabled(true);
				}
			});
			outerParBetaField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					outerPar.Beta = outerParBetaField.getValue();
					outerApplyButton.setEnabled(true);
				}
			});
			outerParHField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					outerPar.H = outerParHField.getValue();
					innerPar.H = outerPar.H;
					innerParHField.setValue(outerPar.H);
					outerApplyButton.setEnabled(true);
					hChanged = true;
				}
			});

			outerParPanel.add(outerParLabelPanel);
			outerParPanel.addGlue();
			outerParPanel.add(outerParFieldPanel);
			outerParPanel.addFixed(10);

			outerApplyButton = new JButton("Apply");
			outerApplyButton.setEnabled(false);
			outerApplyButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					ballAndBeamRegul.setOuterParameters(outerPar);
					if (hChanged) {
						ballAndBeamRegul.setInnerParameters(innerPar);
					}      
					hChanged = false;
					outerApplyButton.setEnabled(false);
				}
			});



			outerParButtonPanel = new BoxPanel(BoxPanel.VERTICAL);
			outerParButtonPanel.setBorder(BorderFactory.createTitledBorder("Outer Parameters"));
			// outerParButtonPanel.http://www.ddg.lth.se/addFixed(10);
			outerParButtonPanel.add(outerParPanel);
			outerParButtonPanel.addFixed(15);
			outerParButtonPanel.add(outerApplyButton);

			leftPanel.add(innerParButtonPanel, BorderLayout.EAST);
			leftPanel.add(outerParButtonPanel, BorderLayout.WEST);
			


			extendedPanel = new BoxPanel(BoxPanel.HORIZONTAL);
			extendedNamePanel = new JPanel();
			extendedNamePanel.setLayout(new GridLayout(0,1));
			extendedNamePanel.add(new JLabel("Angle: "));
			extendedNamePanel.add(new JLabel("Step: "));
			extendedNamePanel.add(new JLabel("Weight: "));
			extendedParPanel = new JPanel();
			extendedParPanel.setLayout(new GridLayout(0,1));
			extendedParPanel.add(currentAngle);
			extendedParPanel.add(currentStep);
			extendedParPanel.add(weight);
			currentAngle.setText("Not started");
			currentStep.setText("Not Started");
			weight.setText("UNKNOWN");

			// Add label and field panels to parameter panel
			extendedPanel.add(extendedNamePanel);
			extendedPanel.addGlue();
			extendedPanel.add(extendedParPanel);
			extendedPanel.addFixed(100);


			rightPanel = new JPanel();
			rightPanel.setLayout(new BorderLayout());
			rightPanel.add(extendedPanel, BorderLayout.WEST);
			//leftPanel.add(outerParButtonPanel, BorderLayout.EAST);
			//leftPanel.add(buttonPanel, BorderLayout.SOUTH);




			// Create panel for the entire GUI.
			guiPanel = new BoxPanel(BoxPanel.HORIZONTAL);
			guiPanel.add(leftPanel);
			guiPanel.addGlue();
			guiPanel.add(plotterPanel);
			guiPanel.addGlue();

			console = new JLabel();
			consoelWindow = new JTextArea(4,40);
			//consoelWindow.setPreferredSize(new Dimension(120,40));


			//consoelWindow.setLineWrap(true);
			scroll = new JScrollPane(consoelWindow);
			//scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
			//scroll.setVerticalScrollBarPolicy();

			console.setPreferredSize(new Dimension(20,40));
			consolePanel = new JPanel();
			consolePanel.setLayout(new BorderLayout());
			consolePanel.add(console, BorderLayout.NORTH);
			consolePanel.add(scroll, BorderLayout.CENTER);
			console.setText("Console Window");






			//consoelWindow.setText("");
			guiPanel.addGlue();
			guiPanel.add(rightPanel);
			guiPanel.add(consolePanel);

			// WindowListener that exits the system if the main window is closed.
			frame.addWindowListener(new WindowAdapter() {
				public void windowClosing(WindowEvent e) {
					ballAndBeamRegul.shutDown();
					stopThread();
					System.exit(0);
				}
			});


			// Set guiPanel to be content pane of the frame.
			frame.getContentPane().add(guiPanel, BorderLayout.CENTER);

			// Pack the components of the window.
			frame.pack();

			// Position the main window at the screen center.
			Dimension sd = Toolkit.getDefaultToolkit().getScreenSize();
			Dimension fd = frame.getSize();
			frame.setLocation((sd.width-fd.width)/2, (sd.height-fd.height)/2);



			// Make the window visible.
			frame.setVisible(true);

		}

		if( i == 1 ){


			innerPar = beamRegul.getInnerParameters();
			innerParKField.setValue(innerPar.K);
			innerParTiField.setValue(innerPar.Ti);
			innerParTrField.setValue(innerPar.Tr);
			innerParBetaField.setValue(innerPar.Beta);
			innerParHField.setValue(innerPar.H);
			innerParRefField.setValue(beamRegul.getRef());
			innerParFieldPanel.setBackground(Color.GREEN);
			outerParFieldPanel.setBackground(Color.RED);
			


		} // ENDS HERE


		if( i == 2){

			outerPar = ballAndBeamRegul.getOuterParameters();
			outerParKField.setValue(outerPar.K);
			outerParTiField.setValue(outerPar.Ti);
			outerParTdField.setValue(outerPar.Td);
			outerParNField.setValue(outerPar.N);
			outerParTrField.setValue(outerPar.Tr);
			outerParBetaField.setValue(outerPar.Beta);
			outerParHField.setValue(outerPar.H);
			outerParFieldPanel.setBackground(Color.GREEN);
			innerPar = ballAndBeamRegul.getInnerParameters();
			// Set initial parameter values of the fields
			innerParKField.setValue(innerPar.K);
			innerParTiField.setValue(innerPar.Ti);
			innerParTrField.setValue(innerPar.Tr);
			innerParBetaField.setValue(innerPar.Beta);
			innerParHField.setValue(innerPar.H);
			innerParRefField.setValue(ballAndBeamRegul.getRef());


		}

	}


	/** Called by Regul to put a control signal data point in the buffer. */
	public synchronized void putControlDataPoint(DoublePoint dp) {
		double x = dp.x;
		double y = dp.y;
		controlPlotter.putData(x, y);
	}


	public synchronized void putAngleDataPoint(DoublePoint dp) {
		double x = dp.x;
		double y = dp.y;
		anglePlotter.putData(x, y);
	}



	public synchronized void putPosDataPoint(DoublePoint dp) {
		double x = dp.x;
		double y = dp.y;
		posPlotter.putData(x, y);
	}




	/** Called by Regul to put a measurement data point in the buffer. */
	public synchronized void putMeasurementDataPoint(PlotData pd) {
		double x = pd.x;
		double ref = pd.yref;
		double y = pd.y;
		measurementPlotter.putData(x, ref, y);
	}  


	public void displayAngle(double d){
		String s = Double.toString(d);
		currentAngle.setText(s.substring(0, 3));
	}
	public void displayStep(String s){
		currentStep.setText(s);
	}
	public void displayWeight(String s){
		weight.setText(s);
	}

	public void writeToConsole(String s){
		consoelWindow.setText(consoelWindow.getText()  + s + "\r\n");
	}

	public void addStateMachine(StateMachine stateMachine) {
		this.stateMachine = stateMachine;

	}
	public StateMachine getStateMachine() {
		return stateMachine;

	}

}
